β
Best Practice: Place all configuration files in the Infrastructure layer.
π Suggested Folder: Infrastructure/Configurations
[Entity]Configutaion.cs
IEntityTypeConfiguration<[Entity]>
You donβt need to explicitly define the primary key (PK) if you follow EF Core conventions (Id
or EntityNameId
). However, if you want to be explicit:
public class TicketConfiguration : IEntityTypeConfiguration<Ticket>
{
public void Configure(EntityTypeBuilder<Ticket> builder)
{
builder.HasKey(t => t.Id); // Explicitly defining PK (optional)
builder.Property(t => t.Id)
.ValueGeneratedOnAdd(); // Sets Identity (auto-increment)
}
}
π NOTE: If youβre using a GUID as the ID, you might need
.ValueGeneratedNever()
instead.
Use HasOne()
and WithMany()
for one-to-many relationships.
public class TicketConfiguration : IEntityTypeConfiguration<Ticket>
{
public void Configure(EntityTypeBuilder<Ticket> builder)
{
builder.HasKey(t => t.Id);
// Foreign Key - Ticket to Transportation
builder.HasOne(t => t.Transportation)
.WithMany(tr => tr.Tickets)
.HasForeignKey(t => t.TransportationId)
.OnDelete(DeleteBehavior.Restrict); // Optional: No cascade delete
}
}
If your navigation property doesnβt match the entity name, you should explicitly specify it using HasOne()
and WithMany()
.
builder.HasOne(t => t.Buyer) // Navigation property (Ticket β Account)
.WithMany(a => a.TicketsBought) // Corresponding collection in Account
.HasForeignKey(t => t.BuyerId);
Tip: If your navigation property names don't match table names, always define them explicitly in the Fluent API.
You can manually specify column types using .HasColumnType()
.
nvarchar
with Specific Lengthsbuilder.Property(t => t.TicketNumber)
.IsRequired()
.HasMaxLength(20) // Limits nvarchar length
.HasColumnType("nvarchar(20)");
DATE
Instead of DATETIME2
builder.Property(t => t.PurchaseDate)
.HasColumnType("date"); // Instead of default "datetime2"
π Best Practice: Always set string lengths to avoid
nvarchar(MAX)
, which hurts performance.
Use .IsRequired()
for NOT NULL and .HasMaxLength()
for length constraints.
builder.Property(t => t.TicketNumber)
.IsRequired() // NOT NULL
.HasMaxLength(20);
builder.HasIndex(t => t.TicketNumber)
.IsUnique(); // Unique constraint
Hereβs a complete example of a configuration file:
public class TicketConfiguration : IEntityTypeConfiguration<Ticket>
{
public void Configure(EntityTypeBuilder<Ticket> builder)
{
builder.HasKey(t => t.Id);
builder.Property(t => t.TransportationId)
.IsRequired();
builder.Property(t => t.SeatId)
.IsRequired();
builder.Property(t => t.BuyerId)
.IsRequired();
builder.Property(t => t.TravelerId)
.IsRequired();
builder.Property(t => t.CreatedAt)
.IsRequired();
builder.Property(t => t.CompanionId)
.IsRequired(false);
builder.Property(t => t.TicketStatusId)
.IsRequired();
builder.Property(t => t.SerialNumber)
.IsRequired()
.HasMaxLength(50)
.IsUnicode(false);
builder.Property(t => t.Description)
.HasMaxLength(200)
.IsUnicode(false);
// Relationships
builder.HasOne(t => t.Transportation)
.WithMany(t => t.Tickets)
.HasForeignKey(t => t.TransportationId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.Seat)
.WithMany(s => s.Tickets)
.HasForeignKey(t => t.SeatId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.Buyer)
.WithMany(a => a.BoughtTickets)
.HasForeignKey(t => t.BuyerId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.Traveler)
.WithMany(p => p.TraveledTickets)
.HasForeignKey(t => t.TravelerId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.Companion)
.WithMany()
.HasForeignKey(t => t.CompanionId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(t => t.TicketStatus)
.WithMany()
.HasForeignKey(t => t.TicketStatusId)
.OnDelete(DeleteBehavior.Restrict);
}
}
β
Store Configuration Files in: Infrastructure/Configurations
β
Define Foreign Keys: Use HasOne()
and WithMany()
β
Explicitly Define Navigation Properties if the names differ
β
Column Types: Use .HasColumnType()
for nvarchar
, date
, etc.
β
Constraints: Use .IsRequired()
, .HasMaxLength()
, .IsUnique()
In many-to-many relationships, a join table is created to link two entities. This join table typically contains foreign keys referencing the primary keys of the two entities involved in the relationship.
Suppose we have two entities, Student
and Course
, and we want to create a many-to-many relationship between them. We'll create a join table called StudentCourses
.
public class Student : Entity<long>
{
public required string Name { get; set; }
public virtual ICollection<StudentCourse> StudentCourses { get; set; }
}
public class Course : Entity<long>
{
public required string Title { get; set; }
public virtual ICollection<StudentCourse> StudentCourses { get; set; }
}
public class StudentCourse
{
public long StudentId { get; set; }
public virtual Student Student { get; set; }
public long CourseId { get; set; }
public virtual Course Course { get; set; }
}
You would configure the join table using the Fluent API:
public class StudentCourseConfiguration : IEntityTypeConfiguration<StudentCourse>
{
public void Configure(EntityTypeBuilder<StudentCourse> builder)
{
// Composite Primary Key
builder.HasKey(sc => new { sc.StudentId, sc.CourseId });
// Foreign Key Relationships
builder.HasOne(sc => sc.Student)
.WithMany(s => s.StudentCourses)
.HasForeignKey(sc => sc.StudentId);
builder.HasOne(sc => sc.Course)
.WithMany(c => c.StudentCourses)
.HasForeignKey(sc => sc.CourseId);
}
}
Student
to Course
and vice versa.GUIDs (Globally Unique Identifiers) can be used as primary keys in your entities. In EF Core, you can configure them to auto-generate when a new entity is created.
public class SomeEntity
{
public Guid Id { get; set; } = Guid.NewGuid(); // Auto-generate GUID
public string Name { get; set; }
}
When configuring an entity with a GUID as the primary key, you donβt need a specific setup in the configuration, but you can enforce that the Id
is generated on addition.
builder.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("NEWSEQUENTIALID()"); // Optionally use NEWID() for random GUID
Guid.NewGuid()
generates a new GUID when a new entity instance is created.NEWSEQUENTIALID()
in SQL Server, it generates sequential GUIDs, which can improve indexing performance.Here's how you might define an entity with GUIDs in your DbContext
:
public class ApplicationDbContext : DbContext
{
public DbSet<SomeEntity> SomeEntities { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SomeEntity>(builder =>
{
builder.HasKey(e => e.Id);
builder.Property(e => e.Id)
.ValueGeneratedOnAdd()
.HasDefaultValueSql("NEWSEQUENTIALID()");
});
}
}
OnModelCreating
and OnConfiguring
as belowusing AlibabaClone.Domain.Aggregates.AccountAggregates;
using Microsoft.EntityFrameworkCore;
namespace AlibabaClone.Infrastructure
{
public class ApplicationDBContext : DbContext
{
public ApplicationDBContext(DbContextOptions<ApplicationDBContext> options) : base(options)
{
}
public DbSet<Account> Accounts { get; set; }
public DbSet<AccountRole> AccountRoles { get; set; }
public DbSet<Gender> Genders{ get; set; }
public DbSet<Person> People { get; set; }
public DbSet<Role> Roles { get; set; }
//... Add other DbSets as well
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDBContext).Assembly);
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
}
}
Modify appsettings.json
and add the following. (They might have a type or something... search the web to make sure)
Adjust the ConnectionString to meet your needs
{
"ConnectionStrings": {
"DefaultConnection": "Server=YOUR_SERVER;Database=YourDb;User Id=USERNAME;Password=PASSWORD;Trusted_Connection=True;TrustServerCertificate=True"
}
}
{
"ConnectionStrings": {
"DefaultConnection": "Server=YOUR_SERVER;Database=YourDb;Integrated Security=TRUE;Trusted_Connection=True;TrustServerCertificate=True"
}
}
appsettings.json
in gitignore
if you think is needed Program.cs
Program.cs
using Infrastructure.Persistence;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
app.Run();
Add-Migration InitialCreate
Update-Database